import { OpenApi } from "@samchon/openapi";
import { IPointer } from "tstl";
import ts from "typescript";

import { INestiaMigrateConfig } from "../structures/INestiaMigrateConfig";
import { FilePrinter } from "../utils/FilePrinter";
import { MapUtil } from "../utils/MapUtil";
import { StringUtil } from "../utils/StringUtil";
import { NestiaMigrateImportProgrammer } from "./NestiaMigrateImportProgrammer";
import { NestiaMigrateSchemaProgrammer } from "./NestiaMigrateSchemaProgrammer";

export namespace NestiaMigrateDtoProgrammer {
  export interface IModule {
    name: string;
    children: Map<string, IModule>;
    programmer:
      | null
      | ((importer: NestiaMigrateImportProgrammer) => ts.TypeAliasDeclaration);
  }

  export const compose = (props: {
    config: INestiaMigrateConfig;
    components: OpenApi.IComponents;
  }): Map<string, IModule> => {
    const dict: Map<string, IModule> = new Map();
    for (const [key, value] of Object.entries(props.components.schemas ?? {})) {
      const emendedKey: string = key
        .split("/")
        .filter((str) => str.length !== 0)
        .map(StringUtil.escapeNonVariable)
        .join("");
      prepare(dict)(emendedKey)((importer) =>
        writeAlias(props.config)(props.components)(importer)(emendedKey, value),
      );
    }
    return dict;
  };

  const prepare =
    (dict: Map<string, IModule>) =>
    (name: string) =>
    (
      programmer: (
        importer: NestiaMigrateImportProgrammer,
      ) => ts.TypeAliasDeclaration,
    ) => {
      const accessors: string[] = name.split(".");
      const modulo: IPointer<IModule> = { value: null! };

      accessors.forEach((acc, i) => {
        modulo.value = MapUtil.take(dict)(acc)(() => ({
          name: acc,
          children: new Map(),
          programmer: null,
        }));
        if (i === accessors.length - 1) modulo.value.programmer = programmer;
        dict = modulo.value.children;
      });
      return modulo!;
    };

  const writeAlias =
    (config: INestiaMigrateConfig) =>
    (components: OpenApi.IComponents) =>
    (importer: NestiaMigrateImportProgrammer) =>
    (key: string, value: OpenApi.IJsonSchema) =>
      FilePrinter.description(
        ts.factory.createTypeAliasDeclaration(
          [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
          key.split(".").at(-1)!,
          [],
          NestiaMigrateSchemaProgrammer.write({
            components,
            importer,
            schema: value,
          }),
        ),
        writeComment(config)(value, key.indexOf(".") === -1),
      );
}

const writeComment =
  (config: INestiaMigrateConfig) =>
  (schema: OpenApi.IJsonSchema, top: boolean): string =>
    [
      ...(schema.description?.length ? [schema.description] : []),
      ...(schema.description?.length &&
      (schema.title !== undefined || schema.deprecated === true)
        ? [""]
        : []),
      ...(schema.title !== undefined ? [`@title ${schema.title}`] : []),
      ...(schema.deprecated === true ? [`@deprecated`] : []),
      ...(top
        ? [
            `@${config.author?.tag ?? "nestia"} ${config.author?.value ?? "Generated by Nestia - https://github.com/samchon/nestia"}`,
          ]
        : []),
    ].join("\n");
